home *** CD-ROM | disk | FTP | other *** search
/ Languguage OS 2 / Languguage OS II Version 10-94 (Knowledge Media)(1994).ISO / gnu / elib-006.lha / elib-0.06 / library / cookie.el < prev    next >
Text File  |  1993-01-24  |  38KB  |  1,131 lines

  1. ;;; $Id: cookie.el,v 1.26 1993/01/18 17:10:51 ceder Exp $
  2. ;;; cookie.el -- Utility to display cookies in buffers
  3. ;;; Copyright (C) 1991, 1992   Per Cederqvist, Inge Wallin
  4. ;;;
  5. ;;; This program is free software; you can redistribute it and/or modify
  6. ;;; it under the terms of the GNU General Public License as published by
  7. ;;; the Free Software Foundation; either version 2 of the License, or
  8. ;;; (at your option) any later version.
  9. ;;;
  10. ;;; This program is distributed in the hope that it will be useful,
  11. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. ;;; GNU General Public License for more details.
  14. ;;;
  15. ;;; You should have received a copy of the GNU General Public License
  16. ;;; along with this program; if not, write to the Free Software
  17. ;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  18.  
  19.  
  20. ;;; Note that this file is still under development.  Comments,
  21. ;;; enhancements and bug fixes are welcome.
  22. ;;; Send them to ceder@lysator.liu.se.
  23.  
  24. ;;; FIXME-now. The pretty-printer should insert the string into the
  25. ;;; buffer. Why?
  26. ;;;     * Faster.
  27. ;;;     * Makes it possible to have collections as cookies.
  28.  
  29. ;;;     Introduction
  30. ;;;     ============
  31. ;;;
  32. ;;; Cookie is a package that implements a connection between an
  33. ;;; dll (a doubly linked list) and the contents of a buffer.
  34. ;;; Possible uses are dired (have all files in a list, and show them),
  35. ;;; buffer-list, kom-prioritize (in the LysKOM elisp client) and
  36. ;;; others.  pcl-cvs.el uses cookie.el.
  37. ;;;
  38. ;;; A `cookie' can be any lisp object.  When you use the cookie
  39. ;;; package you specify a pretty-printer, a function that inserts
  40. ;;; a printable representation of the cookie in the buffer.  (The
  41. ;;; pretty-printer should use "insert" and not
  42. ;;; "insert-before-markers").
  43. ;;;
  44. ;;; A `collection' consists of a doubly linked list of cookies, a
  45. ;;; header, a footer and a pretty-printer.  It is displayed at a
  46. ;;; certain point in a certain buffer.  (The buffer and point are
  47. ;;; fixed when the collection is created).  The header and the footer
  48. ;;; are constant strings.  They appear before and after the cookies.
  49. ;;; (Currently, once set, they can not be changed).
  50. ;;;
  51. ;;; Cookie does not affect the mode of the buffer in any way. It
  52. ;;; merely makes it easy to connect an underlying data representation
  53. ;;; to the buffer contents.
  54. ;;;
  55. ;;; A `tin' is an object that contains one cookie.  There are
  56. ;;; functions in this package that given a tin extracts the cookie, or
  57. ;;; gives the next or previous tin.  (All tins are linked together in
  58. ;;; a doubly linked list.  The 'previous' tin is the one that appears
  59. ;;; before the other in the buffer.)  You should not do anything with
  60. ;;; a tin except pass it to the functions in this package.
  61. ;;;
  62. ;;; A collection is a very dynamic thing.  You can easily add or
  63. ;;; delete cookies.  You can sort all cookies in a collection (you
  64. ;;; have to supply a function that compares two cookies).  You can
  65. ;;; apply a function to all cookies in a collection, et c, et c.
  66. ;;;
  67. ;;; Remember that a cookie can be anything.  Your imagination is the
  68. ;;; limit!  It is even possible to have another collection as a
  69. ;;; cookie.  In that way some kind of tree hierarchy can be created.
  70. ;;;
  71. ;;; Full documentation will, God willing, soon be available in a
  72. ;;; TeXinfo manual.
  73.  
  74.  
  75.  
  76. ;;;     Coding conventions
  77. ;;;     ==================
  78. ;;;
  79. ;;; All functions that are intended for external use begin with one of
  80. ;;; the prefixes "cookie-", "collection-" or "tin-".  The prefix
  81. ;;; "icookie-" is used for internal functions and macros.  There are
  82. ;;; currently no global or buffer-local variables used.
  83. ;;;
  84. ;;; Many function operate on `tins' instead of `cookies'.  To avoid
  85. ;;; confusion most of the function names include the string "cookie"
  86. ;;; or "tin" to show this.
  87. ;;;
  88. ;;; Most doc-strings contains an "Args:" line that lists the
  89. ;;; arguments.
  90. ;;;
  91. ;;; The internal functions don't contain any doc-strings.  RMS thinks
  92. ;;; this is a good way to save space.
  93.  
  94.  
  95.  
  96. ;;; INTERNAL DOCUMENTATION (Your understanding of this package might
  97. ;;; increase if you read it, but you should not exploit the knowledge
  98. ;;; you gain. The internal details might change without notice).
  99. ;;;
  100. ;;; A collection is implemented as an dll (a doubly linked list).
  101. ;;; The first and last element on the list are always the header and
  102. ;;; footer (as strings). Any remaining entries are `wrappers'.
  103. ;;;
  104. ;;; At the implementation level a `tin' is really an elib-node that
  105. ;;; consists of
  106. ;;;      left        Pointer to previous tin
  107. ;;;      right       Pointer to next tin
  108. ;;;      data        Holder of a `wrapper'.
  109. ;;; These internals of an elib-node are in fact unknown to cookie.el.
  110. ;;; It uses dll.el to handle everything that deals with the
  111. ;;; doubly linked list.
  112. ;;;
  113. ;;; The wrapper data type contains
  114. ;;;      start-marker    Position of the printed representation of the
  115. ;;;                      cookie in the buffer. 
  116. ;;;      cookie          The user-supplied cookie.
  117. ;;;
  118. ;;; The wrapper is not accessible to the user of this package.
  119.  
  120. (require 'dll)
  121. (provide 'cookie)
  122.  
  123.  
  124. ;;; ================================================================
  125. ;;;      Internal   macros   for use in the cookie package
  126.  
  127.  
  128. (put 'icookie-set-buffer-bind-dll 'lisp-indent-hook 1)
  129.  
  130. (defmacro icookie-set-buffer-bind-dll (collection &rest forms)
  131.  
  132.   ;; Execute FORMS with collection->buffer selected as current buffer,
  133.   ;; and dll bound to collection->dll.
  134.   ;; Return value of last form in FORMS.  INTERNAL USE ONLY.
  135.  
  136.   (let ((old-buffer (make-symbol "old-buffer"))
  137.     (hnd (make-symbol "collection")))
  138.     (` (let* (((, old-buffer) (current-buffer))
  139.           ((, hnd) (, collection))
  140.           (dll (icookie-collection->dll (, hnd))))
  141.      (set-buffer (icookie-collection->buffer (, hnd)))
  142.      (unwind-protect
  143.          (progn (,@ forms))
  144.        (set-buffer (, old-buffer)))))))
  145.  
  146.  
  147. (put 'icookie-set-buffer-bind-dll-let* 'lisp-indent-hook 2)
  148.  
  149. (defmacro icookie-set-buffer-bind-dll-let* (collection varlist &rest forms)
  150.  
  151.   ;; Execute FORMS with collection->buffer selected as current buffer,
  152.   ;; dll bound to collection->dll, and VARLIST bound as in a let*.
  153.   ;; dll will be bound when VARLIST is initialized, but the current
  154.   ;; buffer will *not* have been changed.
  155.   ;; Return value of last form in FORMS.  INTERNAL USE ONLY.
  156.  
  157.   (let ((old-buffer (make-symbol "old-buffer"))
  158.     (hnd (make-symbol "collection")))
  159.     (` (let* (((, old-buffer) (current-buffer))
  160.           ((, hnd) (, collection))
  161.           (dll (icookie-collection->dll (, hnd)))
  162.           (,@ varlist))
  163.      (set-buffer (icookie-collection->buffer (, hnd)))
  164.      (unwind-protect
  165.          (progn (,@ forms))
  166.        (set-buffer (, old-buffer)))))))
  167.  
  168.  
  169. (defmacro icookie-filter-hf (collection tin)
  170.  
  171.   ;; Evaluate TIN once and return it. BUT if it is
  172.   ;; the header or the footer in COLLECTION return nil instead.
  173.   ;; Args: COLLECTION TIN
  174.   ;; INTERNAL USE ONLY.
  175.  
  176.   (let ((tempvar (make-symbol "tin"))
  177.     (tmpcoll (make-symbol "tmpcollection")))
  178.     (` (let (((, tempvar) (, tin))
  179.          ((, tmpcoll) (, collection)))
  180.      (if (or (eq (, tempvar) (icookie-collection->header (, tmpcoll)))
  181.          (eq (, tempvar) (icookie-collection->footer (, tmpcoll))))
  182.          nil
  183.        (, tempvar))))))
  184.  
  185.  
  186.  
  187. ;;; ================================================================
  188. ;;;      Internal   data types   for use in the cookie package
  189.  
  190. ;;; Yes, I know about cl.el, but I don't like it.   /ceder
  191.  
  192. ;;; The wrapper data type.
  193.  
  194. (defun icookie-create-wrapper (start-marker cookie)
  195.   ;; Create a wrapper.   INTERNAL USE ONLY.
  196.   (cons 'WRAPPER (vector start-marker cookie)))
  197.  
  198. (defun icookie-wrapper->start-marker (wrapper)
  199.   ;; Get start-marker from wrapper.    INTERNAL USE ONLY.
  200.   (elt (cdr wrapper) 0))
  201.  
  202. (defun icookie-wrapper->cookie-safe (wrapper)
  203.   ;; Get cookie from wrapper.   INTERNAL USE ONLY.
  204.   ;; Returns nil if given nil as input.
  205.   ;; Since (elt nil 1) returns nil in emacs version 18.57 and 18.58
  206.   ;; this can be defined in this way. The documentation in the info
  207.   ;; file says that elt should signal an error in that case. I think
  208.   ;; it is the documentation that is buggy. (The bug is reported).
  209.   (elt (cdr wrapper) 1))
  210.  
  211. (defun icookie-wrapper->cookie (wrapper)
  212.   ;; Get cookie from wrapper.   INTERNAL USE ONLY.
  213.   (elt (cdr wrapper) 1))
  214.  
  215.  
  216.  
  217. ;;; The collection data type
  218.  
  219. (defun icookie-create-collection (buffer pretty-printer 
  220.                      header-wrapper footer-wrapper
  221.                      dll)
  222.   ;; Create a collection. INTERNAL USE ONLY.
  223.   (cons 'COLLECTION
  224.     ;; The last element is a pointer to the last tin
  225.     ;; the cursor was at, or nil if that is unknown.  
  226.     (vector buffer
  227.         pretty-printer 
  228.         header-wrapper footer-wrapper
  229.         dll nil)))
  230.  
  231.  
  232. (defun icookie-collection->buffer (collection)
  233.   ;; Get buffer from COLLECTION.
  234.   (elt (cdr collection) 0))
  235.  
  236. (defun icookie-collection->pretty-printer (collection)
  237.   ;; Get pretty-printer from COLLECTION.
  238.   (elt (cdr collection) 1))
  239.  
  240. (defun icookie-collection->header (collection)
  241.   ;; Get header from COLLECTION.
  242.   (elt (cdr collection) 2))
  243.  
  244. (defun icookie-collection->footer (collection)
  245.   ;; Get footer from COLLECTION.
  246.   (elt (cdr collection) 3))
  247.  
  248. (defun icookie-collection->dll (collection)
  249.   ;; Get dll from COLLECTION.
  250.   (elt (cdr collection) 4))
  251.  
  252. (defun icookie-collection->last-tin (collection)
  253.   ;; Get last-tin from COLLECTION.
  254.   (elt (cdr collection) 5))
  255.  
  256.  
  257.  
  258. (defun icookie-set-collection->buffer (collection buffer)
  259.   ;; Change the buffer. Args: COLLECTION BUFFER.
  260.   (aset (cdr collection) 0 buffer))
  261.  
  262. (defun icookie-set-collection->pretty-printer (collection pretty-printer)
  263.   ;; Change the pretty-printer. Args: COLLECTION PRETTY-PRINTER.
  264.   (aset (cdr collection) 1 pretty-printer))
  265.  
  266. (defun icookie-set-collection->header (collection header)
  267.   ;; Change the header. Args: COLLECTION HEADER.
  268.   (aset (cdr collection) 2 header))
  269.  
  270. (defun icookie-set-collection->footer (collection footer)
  271.   ;; Change the footer. Args: COLLECTION FOOTER.
  272.   (aset (cdr collection) 3 footer))
  273.  
  274. (defun icookie-set-collection->dll (collection dll)
  275.   ;; Change the dll. Args: COLLECTION DLL.
  276.   (aset (cdr collection) 4 dll))
  277.  
  278. (defun icookie-set-collection->last-tin (collection last-tin)
  279.   ;; Change the last-tin. Args: COLLECTION LAST-TIN.
  280.   (aset (cdr collection) 5 last-tin))
  281.  
  282.  
  283. ;;; ================================================================
  284. ;;;      Internal   functions   for use in the cookie package
  285.  
  286. (defun icookie-abs (x)
  287.   ;; Return the absolute value of x
  288.   (max x (- x)))
  289.  
  290. (defun icookie-create-wrapper-and-insert (cookie string pos)
  291.   ;; Insert STRING at POS in current buffer. Remember the start
  292.   ;; position. Create a wrapper containing that start position and the
  293.   ;; COOKIE.
  294.   ;;    INTERNAL USE ONLY.
  295.  
  296.   (save-excursion
  297.     (goto-char pos)
  298.     ;; Remember the position as a number so that it doesn't move
  299.     ;; when we insert the string.
  300.     (let ((start (if (markerp pos)
  301.              (marker-position pos)
  302.            pos))
  303.       (buffer-read-only nil))
  304.       ;; Use insert-before-markers so that the marker for the
  305.       ;; next cookie is updated.
  306.       (insert-before-markers string)
  307.  
  308.       ;; Always insert a newline. You want invisible cookies? You
  309.       ;; lose. (At least in this version). FIXME-someday. (It is
  310.       ;; harder to fix than it might seem. All markers have to point
  311.       ;; to the right place all the time...)
  312.       (insert-before-markers ?\n)
  313.       (icookie-create-wrapper (copy-marker start) cookie))))
  314.  
  315.  
  316. (defun icookie-create-wrapper-and-pretty-print (cookie
  317.                         pretty-printer pos)
  318.   ;; Call PRETTY-PRINTER with point set at POS in current buffer.
  319.   ;; Remember the start position. Create a wrapper containing that
  320.   ;; start position and the COOKIE.
  321.   ;;    INTERNAL USE ONLY.
  322.  
  323.   (save-excursion
  324.     (goto-char pos)
  325.     ;; Remember the position as a number so that it doesn't move
  326.     ;; when we insert the string.
  327.     (let ((start (if (markerp pos)
  328.              (marker-position pos)
  329.            pos))
  330.       (buffer-read-only nil))
  331.       ;; Insert the trailing newline using insert-before-markers
  332.       ;; so that the start position for the next cookie is updated.
  333.       (insert-before-markers ?\n)
  334.       ;; Move back, and call the pretty-printer.
  335.       (backward-char 1)
  336.       (funcall pretty-printer cookie)
  337.       (icookie-create-wrapper (copy-marker start) cookie))))
  338.  
  339.  
  340. (defun icookie-delete-tin-internal (collection tin)
  341.   ;; Delete a cookie string from COLLECTION.  INTERNAL USE ONLY.
  342.   ;; Can not be used on the footer. Returns the wrapper that is deleted.
  343.   ;; The start-marker in the wrapper is set to nil, so that it doesn't
  344.   ;; consume any more resources.
  345.   (let ((dll (icookie-collection->dll collection))
  346.     (buffer-read-only nil))
  347.     (delete-region (icookie-wrapper->start-marker (dll-element dll tin))
  348.            (icookie-wrapper->start-marker
  349.             (dll-element dll (dll-next dll tin))))
  350.     (set-marker (icookie-wrapper->start-marker (dll-element dll tin)) nil)
  351.     ;; Delete the tin, and return the wrapper.
  352.     (dll-delete dll tin)))
  353.  
  354. (defun icookie-refresh-tin (collection tin)
  355.   ;; Redisplay the cookie represented by TIN. INTERNAL USE ONLY.
  356.   ;; Args: COLLECTION TIN
  357.   ;; Can not be used on the footer. dll *must* be bound to
  358.   ;; (icookie-collection->dll collection).
  359.  
  360.   (let ((buffer-read-only nil))
  361.     (save-excursion
  362.       ;; First, remove the string from the buffer:
  363.       (delete-region (icookie-wrapper->start-marker (dll-element dll tin))
  364.              (1- (marker-position
  365.               (icookie-wrapper->start-marker
  366.                (dll-element dll (dll-next dll tin))))))
  367.  
  368.       ;; Calculate and insert the string.
  369.  
  370.       (goto-char (icookie-wrapper->start-marker (dll-element dll tin)))
  371.       (funcall (icookie-collection->pretty-printer collection)
  372.            (icookie-wrapper->cookie (dll-element dll tin))))))
  373.  
  374.  
  375. (defun icookie-pos-before-middle-p (collection pos tin1 tin2)
  376.  
  377.   ;; Return true if for the cookies in COLLECTION, POS is in the first
  378.   ;; half of the region defined by TIN1 and TIN2.
  379.  
  380.   (let ((dll (icookie-collection->dll collection)))
  381.     (< pos (/ (+ (icookie-wrapper->start-marker (dll-element dll tin1))
  382.          (icookie-wrapper->start-marker (dll-element dll tin2)))
  383.           2))))
  384.  
  385.  
  386. ;;; ===========================================================================
  387. ;;;                  Public members of the cookie package
  388.  
  389.  
  390. (defun collection-create (buffer pretty-printer 
  391.                  &optional header footer pos)
  392.   "Create an empty collection of cookies.
  393. Args: BUFFER PRETTY-PRINTER &optional HEADER FOOTER POS.
  394.  
  395. The collection will be inserted in BUFFER. BUFFER may be a
  396. buffer or a buffer name. It is created if it does not exist.
  397.  
  398. PRETTY-PRINTER should be a function that takes one argument, a
  399. cookie, and inserts a string representing it in the buffer (at
  400. point). The string PRETTY-PRINTER inserts may be empty or span
  401. several linse. A trailing newline will always be inserted
  402. automatically. The PRETTY-PRINTER should use insert, and not
  403. insert-before-markers.
  404.  
  405. Optional third argument HEADER is a string that will always be
  406. present at the top of the collection. HEADER should end with a
  407. newline.  Optionaly fourth argument FOOTER is similar, and will
  408. always be inserted at the bottom of the collection.
  409.  
  410. Optional fifth argument POS is a buffer position, specifying
  411. where the collection will be inserted. It defaults to the
  412. begining of the buffer."
  413.  
  414.   (let ((new-collection
  415.      (icookie-create-collection (get-buffer-create buffer)
  416.                     pretty-printer nil nil (dll-create))))
  417.  
  418.     (icookie-set-buffer-bind-dll new-collection
  419.       ;; Set default values
  420.       (if (not header)
  421.       (setq header ""))
  422.       (if (not footer)
  423.       (setq footer ""))
  424.       (if (not pos)
  425.       (setq pos (point-min))
  426.     (if (markerp pos)
  427.         (set pos (marker-position pos)))) ;Force header to be above footer.
  428.  
  429.       (let ((foot (icookie-create-wrapper-and-insert footer footer pos))
  430.         (head (icookie-create-wrapper-and-insert header header pos)))
  431.  
  432.     (dll-enter-first dll head)
  433.     (dll-enter-last  dll foot)
  434.     (icookie-set-collection->header new-collection (dll-nth dll 0))
  435.     (icookie-set-collection->footer new-collection (dll-nth dll -1))))
  436.  
  437.     ;; Return the collection
  438.     new-collection))
  439.  
  440.  
  441. (defun tin-cookie (collection tin)
  442.   "Get the cookie from a TIN. Args: COLLECTION TIN."
  443.   (icookie-wrapper->cookie (dll-element (cookie->dll collection) tin)))
  444.  
  445. (defun cookie-enter-first (collection cookie)
  446.   "Enter a COOKIE first in the cookie collection COLLECTION.
  447. Args: COLLECTION COOKIE."
  448.  
  449.   (icookie-set-buffer-bind-dll collection
  450.  
  451.     ;; It is always safe to insert an element after the first element,
  452.     ;; because the header is always present. (dll-nth dll 0) should
  453.     ;; therefore never return nil.
  454.  
  455.     (dll-enter-after
  456.      dll
  457.      (dll-nth dll 0)
  458.      (icookie-create-wrapper-and-pretty-print
  459.       cookie
  460.       (icookie-collection->pretty-printer collection)
  461.       (icookie-wrapper->start-marker
  462.        (dll-element dll (dll-nth dll 1)))))))
  463.  
  464.  
  465. (defun cookie-enter-last (collection cookie)
  466.   "Enter a COOKIE last in the cookie-collection COLLECTION.
  467. Args: COLLECTION COOKIE."
  468.  
  469.   (icookie-set-buffer-bind-dll collection
  470.  
  471.     ;; Remember that the header and footer are always present. There
  472.     ;; is no need to check if (dll-nth dll -1) returns nil - it never
  473.     ;; does.
  474.  
  475.     (dll-enter-before
  476.      dll
  477.      (dll-nth dll -1)
  478.      (icookie-create-wrapper-and-pretty-print
  479.       cookie
  480.       (icookie-collection->pretty-printer collection)
  481.       (icookie-wrapper->start-marker (dll-last dll))))))
  482.  
  483.  
  484. (defun cookie-enter-after-tin (collection tin cookie)
  485.   "Enter a new COOKIE after TIN.
  486. Args: COLLECTION TIN COOKIE."
  487.   (icookie-set-buffer-bind-dll collection
  488.     (dll-enter-after
  489.      dll tin
  490.      (icookie-create-wrapper-and-pretty-print
  491.       cookie
  492.       (icookie-collection->pretty-printer collection)
  493.       (icookie-wrapper->start-marker (dll-element dll (dll-next dll tin)))))))
  494.  
  495.  
  496. (defun cookie-enter-before-tin (collection tin cookie)
  497.   "Enter a new COOKIE before TIN.
  498. Args: COLLECTION TIN COOKIE."
  499.   (icookie-set-buffer-bind-dll collection
  500.     (dll-enter-before
  501.      dll tin
  502.      (icookie-create-wrapper-and-pretty-print
  503.       cookie
  504.       (icookie-collection->pretty-printer collection)
  505.       (icookie-wrapper->start-marker (dll-element dll tin))))))
  506.  
  507.  
  508. (defun tin-next (collection tin)
  509.   "Get the next tin. Args: COLLECTION TIN.
  510. Returns nil if TIN is nil or the last cookie."
  511.   (if tin
  512.       (icookie-filter-hf
  513.        collection (dll-next (icookie-collection->dll collection) tin))
  514.     nil))
  515.  
  516. (defun tin-previous (collection tin)
  517.   "Get the previous tin. Args: COLLECTION TIN.
  518. Returns nil if TIN is nil or the first cookie."
  519.   (if tin
  520.       (icookie-filter-hf
  521.        collection
  522.        (dll-previous (icookie-collection->dll collection) tin))
  523.     nil))
  524.  
  525.  
  526. (defun tin-nth (collection n)
  527.   "Return the Nth tin. Args: COLLECTION N.
  528. N counts from zero. Nil is returned if there is less than N cookies.
  529. If N is negative, return the -(N+1)th last element.
  530. Thus, (tin-nth dll 0) returns the first node,
  531. and (tin-nth dll -1) returns the last node.
  532.  
  533. Use tin-cookie to extract the cookie from the tin (or use
  534. cookie-nth instead)."
  535.  
  536.     ;; Skip the header (or footer, if n is negative).
  537.     (if (< n 0)
  538.     (setq n (1- n))
  539.       (setq n (1+ n)))
  540.  
  541.     (icookie-filter-hf collection
  542.                (dll-nth (icookie-collection->dll collection) n)))
  543.  
  544. (defun cookie-nth (collection n)
  545.   "Return the Nth cookie. Args: COLLECTION N.
  546. N counts from zero. Nil is returned if there is less than N cookies.
  547. If N is negative, return the -(N+1)th last element.
  548. Thus, (cookie-nth dll 0) returns the first cookie,
  549. and (cookie-nth dll -1) returns the last cookie."
  550.  
  551.     ;; Skip the header (or footer, if n is negative).
  552.     (if (< n 0)
  553.     (setq n (1- n))
  554.       (setq n (1+ n)))
  555.  
  556.     (let* ((dll (icookie-collection->dll collection))
  557.        (tin (icookie-filter-hf collection (dll-nth dll n))))
  558.       (if tin
  559.       (icookie-wrapper->cookie (dll-element dll tin))
  560.     nil)))
  561.  
  562. (defun tin-delete (collection tin)
  563.   "Delete a tin from a collection. Args: COLLECTION TIN.
  564. The cookie in the tin is returned."
  565.  
  566.   ;; If we are about to delete the tin pointed at by last-tin,
  567.   ;; set last-tin to nil.
  568.   (if (eq (icookie-collection->last-tin collection) tin)
  569.       (icookie-set-collection->last-tin collection nil))
  570.  
  571.   (icookie-set-buffer-bind-dll collection
  572.     (icookie-wrapper->cookie (icookie-delete-tin-internal collection tin))))
  573.  
  574.  
  575. (defun cookie-delete-first (collection)
  576.   "Delete first cookie and return it. Args: COLLECTION.
  577. Returns nil if there are no cookies left in the collection."
  578.  
  579.   (icookie-set-buffer-bind-dll-let* collection
  580.       ((tin (dll-nth dll 1)))         ;Skip the header.
  581.  
  582.     ;; We have to check that we do not try to delete the footer.
  583.     (if (eq tin (icookie-collection->footer collection))
  584.     nil
  585.       (icookie-wrapper->cookie (icookie-delete-tin-internal collection tin)))))
  586.  
  587.  
  588. (defun cookie-delete-last (collection)
  589.   "Delete last cookie and return it. Args: COLLECTION.
  590. Returns nil if there is no cookie left in the collection."
  591.  
  592.   (icookie-set-buffer-bind-dll-let* collection
  593.       ((tin (dll-nth dll -2)))        ;Skip the footer.
  594.     ;; We have to check that we do not try to delete the header.
  595.     (if (eq tin (icookie-collection->header collection))
  596.     nil
  597.       (icookie-wrapper->cookie (icookie-delete-tin-internal collection tin)))))
  598.  
  599. (defun cookie-first (collection)
  600.   "Return the first cookie in COLLECTION. The cookie is not removed."
  601.  
  602.   (let* ((dll (icookie-collection->dll collection))
  603.      (tin (icookie-filter-hf collection (dll-nth dll -1))))
  604.     (if tin
  605.     (icookie-wrapper->cookie (dll-element dll tin)))))
  606.  
  607.  
  608.  
  609. (defun cookie-last (collection)
  610.   "Return the last cookie in COLLECTION. The cookie is not removed."
  611.  
  612.   (let* ((dll (icookie-collection->dll collection))
  613.      (tin (icookie-filter-hf collection (dll-nth dll -2))))
  614.       (if tin
  615.       (icookie-wrapper->cookie (dll-element dll tin)))))
  616.  
  617.  
  618. (defun collection-empty (collection)
  619.   "Return true if there are no cookies in COLLECTION."
  620.  
  621.   (eq (dll-nth (icookie-collection->dll collection) 1) 
  622.       (icookie-collection->footer collection)))
  623.  
  624.  
  625. (defun collection-length (collection)
  626.   "Return the number of cookies in COLLECTION."
  627.  
  628.   ;; Don't count the footer and header.
  629.   (- (dll-length (icookie-collection->dll collection) 2)))
  630.  
  631.  
  632. (defun collection-list-cookies (collection)
  633.   "Return a list of all cookies in COLLECTION."
  634.  
  635.   (icookie-set-buffer-bind-dll-let* collection
  636.       ((result nil)
  637.        (header (icookie-collection->header collection))
  638.        (tin (dll-nth dll -2)))
  639.     (while (not (eq tin header))
  640.       (setq result (cons (icookie-wrapper->cookie (dll-element dll tin))
  641.              result))
  642.       (setq tin (dll-previous dll tin)))
  643.     result))
  644.  
  645.  
  646. (defun collection-clear (collection)
  647.   "Remove all cookies in COLLECTION."
  648.  
  649.   (icookie-set-buffer-bind-dll-let* collection
  650.       ((header (icookie-collection->header collection))
  651.        (footer (icookie-collection->footer collection)))
  652.  
  653.     ;; We have to bind buffer-read-only separately, so that the
  654.     ;; current buffer is correct.
  655.     (let ((buffer-read-only nil))
  656.       (delete-region (icookie-wrapper->start-marker
  657.               (dll-element dll (dll-nth dll 1)))
  658.              (icookie-wrapper->start-marker
  659.               (dll-element dll footer))))
  660.     (setq dll (dll-create-from-list (list (dll-element dll header)
  661.                       (dll-element dll footer))))
  662.     (icookie-set-collection->dll collection dll)
  663.  
  664.     ;; Re-set the header and footer, since they are now new objects.
  665.     ;; icookie-filter-hf uses eq to compare objects to them...
  666.     (icookie-set-collection->header collection (dll-nth dll 0))
  667.     (icookie-set-collection->footer collection (dll-nth dll -1))))
  668.  
  669.  
  670. (defun cookie-map (map-function collection &rest map-args)
  671.   "Apply MAP-FUNCTION to all cookies in COLLECTION.
  672. MAP-FUNCTION is applied to the first element first.
  673. If MAP-FUNCTION returns non-nil the cookie will be refreshed (its
  674. pretty-printer will be called once again).
  675.  
  676. Note that the buffer for COLLECTION will be current buffer when MAP-FUNCTION 
  677. is called.  MAP-FUNCTION must restore the current buffer to BUFFER before 
  678. it returns, if it changes it.
  679.  
  680. If more than two arguments are given to cookie-map, remaining
  681. arguments will be passed to MAP-FUNCTION."
  682.  
  683.   (icookie-set-buffer-bind-dll-let* collection
  684.       ((footer (icookie-collection->footer collection))
  685.        (tin (dll-nth dll 1)))
  686.  
  687.     (while (not (eq tin footer))
  688.  
  689.       (if (apply map-function
  690.          (icookie-wrapper->cookie (dll-element dll tin))
  691.          map-args)
  692.       (icookie-refresh-tin collection tin))
  693.  
  694.       (setq tin (dll-next dll tin)))))
  695.  
  696.  
  697.  
  698. (defun cookie-map-reverse (map-function collection &rest map-args)
  699.   "Apply MAP-FUNCTION to all cookies in COLLECTION.
  700. MAP-FUNCTION is applied to the last cookie first.
  701. If MAP-FUNCTION returns non-nil the cookie will be refreshed.
  702.  
  703. Note that the buffer for COLLECTION will be current buffer when MAP-FUNCTION 
  704. is called.  MAP-FUNCTION must restore the current buffer to BUFFER before 
  705. it returns, if it changes the current buffer.
  706.  
  707. If more than two arguments are given to cookie-map, remaining
  708. arguments will be passed to MAP-FUNCTION."
  709.  
  710.   (icookie-set-buffer-bind-dll-let* collection
  711.       ((header (icookie-collection->header collection))
  712.        (tin (dll-nth dll -2)))
  713.  
  714.     (while (not (eq tin header))
  715.  
  716.       (if (apply map-function
  717.          (icookie-wrapper->cookie (dll-element dll tin))
  718.          map-args)
  719.       (icookie-refresh-tin collection tin))
  720.  
  721.       (setq tin (dll-previous dll tin)))))
  722.  
  723.  
  724.  
  725. (defun collection-append-cookies (collection cookie-list)
  726.   "Insert all cookies in the list COOKIE-LIST last in COLLECTION.
  727. Args: COLLECTION COOKIE-LIST."
  728.  
  729.   (while cookie-list
  730.     (cookie-enter-last collection (car cookie-list))
  731.     (setq cookie-list (cdr cookie-list))))
  732.  
  733.  
  734. (defun collection-filter-cookies (collection predicate &rest extra-args)
  735.   "Remove all cookies in COLLECTION for which PREDICATE returns nil.
  736. Args: COLLECTION PREDICATE &rest EXTRA-ARGS.
  737. Note that the buffer for COLLECTION will be current-buffer when PREDICATE 
  738. is called. PREDICATE must restore the current buffer before it returns
  739. if it changes it.
  740.  
  741. The PREDICATE is called with the cookie as its first argument. If any
  742. EXTRA-ARGS are given to collection-filter-cookies they will be passed to the
  743. PREDICATE."
  744.  
  745.   (icookie-set-buffer-bind-dll-let* collection
  746.       ((tin (dll-nth dll 1))
  747.        (footer (icookie-collection->footer collection))
  748.        (next nil))
  749.     (while (not (eq tin footer))
  750.       (setq next (dll-next dll tin))
  751.       (if (apply predicate
  752.          (icookie-wrapper->cookie (dll-element dll tin))
  753.          extra-args)
  754.       nil
  755.     (icookie-delete-tin-internal collection tin))
  756.       (setq tin next))))
  757.  
  758.  
  759. (defun collection-filter-tins (collection predicate &rest extra-args)
  760.   "Remove all cookies in COLLECTION for which PREDICATE returns nil.
  761. Note that the buffer for COLLECTION will be current-buffer when PREDICATE 
  762. is called. PREDICATE must restore the current buffer before it returns
  763. if it changes it.
  764.  
  765. The PREDICATE is called with one argument, the tin. If any EXTRA-ARGS
  766. are given to collection-filter-cookies they will be passed to the PREDICATE."
  767.  
  768.   (icookie-set-buffer-bind-dll-let* collection
  769.       ((tin (dll-nth dll 1))
  770.        (footer (icookie-collection->footer collection))
  771.        (next nil))
  772.     (while (not (eq tin footer))
  773.       (setq next (dll-next dll tin))
  774.       (if (apply predicate tin extra-args)
  775.       nil
  776.     (icookie-delete-tin-internal collection tin))
  777.       (setq tin next))))
  778.  
  779.  
  780. (defun tin-locate (collection pos &optional guess)
  781.   "Return the tin that POS (a buffer position) is within.
  782. Args: COLLECTION POS &optional GUESS.
  783. POS may be a marker or an integer.
  784. GUESS should be a tin that it is likely that POS is near.
  785.  
  786. If POS points before the first cookie, the first cookie is returned.
  787. If POS points after the last cookie, the last cookie is returned.
  788. If the COLLECTION is empty, nil is returned."
  789.  
  790.   (icookie-set-buffer-bind-dll-let* collection
  791.       ((footer (icookie-collection->footer collection)))
  792.  
  793.     (cond
  794.      ;; No cookies present?
  795.      ((eq (dll-nth dll 1) (dll-nth dll -1))
  796.       nil)
  797.  
  798.      ;; Before first cookie?
  799.      ((< pos (icookie-wrapper->start-marker
  800.           (dll-element dll (dll-nth dll 1))))
  801.       (dll-nth dll 1))
  802.  
  803.      ;; After last cookie?
  804.      ((>= pos (icookie-wrapper->start-marker (dll-last dll)))
  805.       (dll-nth dll -2))
  806.  
  807.      ;; We now now that pos is within a cookie.
  808.      (t
  809.       ;; Make an educated guess about which of the three known
  810.       ;; cookies (the first, the last, or GUESS) is nearest.
  811.       (let* ((best-guess (dll-nth dll 1))
  812.          (distance (icookie-abs (- pos (icookie-wrapper->start-marker
  813.                         (dll-element dll best-guess))))))
  814.     (if guess
  815.         (let* ((g guess)        ;Check the guess, if given.
  816.            (d (icookie-abs
  817.                (- pos (icookie-wrapper->start-marker
  818.                    (dll-element dll g))))))
  819.           (cond
  820.            ((< d distance)
  821.         (setq distance d)
  822.         (setq best-guess g)))))
  823.  
  824.     (let* ((g (dll-nth dll -1))    ;Check the last cookie
  825.            (d (icookie-abs
  826.            (- pos (icookie-wrapper->start-marker
  827.                (dll-element dll g))))))
  828.       (cond
  829.        ((< d distance)
  830.         (setq distance d)
  831.         (setq best-guess g))))
  832.  
  833.     (if (icookie-collection->last-tin collection) ;Check "previous".
  834.         (let* ((g (icookie-collection->last-tin collection)) 
  835.            (d (icookie-abs
  836.                (- pos (icookie-wrapper->start-marker
  837.                    (dll-element dll g))))))
  838.           (cond
  839.            ((< d distance)
  840.         (setq distance d)
  841.         (setq best-guess g)))))
  842.  
  843.     ;; best-guess is now a "best guess".
  844.      
  845.     ;; Find the correct cookie. First determine in which direction
  846.     ;; it lies, and then move in that direction until it is found.
  847.     
  848.     (cond
  849.      ;; Is pos after the guess?
  850.      ((>= pos
  851.           (icookie-wrapper->start-marker (dll-element dll best-guess)))
  852.  
  853.       ;; Loop until we are exactly one cookie too far down...
  854.       (while (>= pos (icookie-wrapper->start-marker
  855.               (dll-element dll best-guess)))
  856.         (setq best-guess (dll-next dll best-guess)))
  857.  
  858.       ;; ...and return the previous cookie.
  859.       (dll-previous dll best-guess))
  860.  
  861.      ;; Pos is before best-guess
  862.      (t
  863.  
  864.       (while (< pos (icookie-wrapper->start-marker
  865.              (dll-element dll best-guess)))
  866.         (setq best-guess (dll-previous dll best-guess)))
  867.  
  868.       best-guess)))))))
  869.  
  870.  
  871. ;;(defun tin-start-marker (collection tin)
  872. ;;  "Return start-position of a cookie in COLLECTION.
  873. ;;Args: COLLECTION TIN.
  874. ;;The marker that is returned should not be modified in any way,
  875. ;;and is only valid until the contents of the cookie buffer changes."
  876. ;;
  877. ;;  (icookie-wrapper->start-marker 
  878. ;;   (dll-element (icookie-collection->dll collection) tin)))
  879.  
  880.  
  881. ;;(defun tin-end-marker (collection tin)
  882. ;;  "Return end-position of a cookie in COLLECTION.
  883. ;;Args: COLLECTION TIN.
  884. ;;The marker that is returned should not be modified in any way,
  885. ;;and is only valid until the contents of the cookie buffer changes."
  886. ;;
  887. ;;  (let ((dll (icookie-collection->dll collection)))
  888. ;;    (icookie-wrapper->start-marker
  889. ;;     (dll-element dll (dll-next dll tin)))))
  890.  
  891.  
  892.  
  893. (defun collection-refresh (collection)
  894.   "Refresh all cookies in COLLECTION.
  895.  
  896. The pretty-printer that was specified when the COLLECTION was created
  897. will be called for all cookies in COLLECTION.
  898.  
  899. Note that tin-invalidate is more efficient if only a small
  900. number of cookies needs to be refreshed."
  901.  
  902.   (icookie-set-buffer-bind-dll-let* collection
  903.  
  904.       ((header (icookie-collection->header collection))
  905.        (footer (icookie-collection->footer collection)))
  906.  
  907.     (let ((buffer-read-only nil))
  908.       (delete-region (icookie-wrapper->start-marker
  909.               (dll-element dll (dll-nth dll 1)))
  910.              (icookie-wrapper->start-marker
  911.               (dll-element dll footer)))
  912.  
  913.       (goto-char (icookie-wrapper->start-marker
  914.           (dll-element dll footer)))
  915.     
  916.       (let ((tin (dll-nth dll 1)))
  917.     (while (not (eq tin footer))
  918.  
  919.       (set-marker (icookie-wrapper->start-marker (dll-element dll tin))
  920.               (point) 
  921.               buffer)
  922.       (funcall (icookie-collection->pretty-printer collection)
  923.            (icookie-wrapper->cookie (dll-element dll tin)))
  924.       (insert "\n")
  925.       (setq tin (dll-next dll tin)))))
  926.     
  927.     (set-marker (icookie-wrapper->start-marker (dll-element dll footer))
  928.         (point)
  929.         buffer)))
  930.  
  931.  
  932. (defun tin-invalidate (collection &rest tins)
  933.   "Refresh some cookies. Args: COLLECTION &rest TINS.
  934. The pretty-printer that for COLLECTION will be called for all TINS."
  935.  
  936.   (icookie-set-buffer-bind-dll collection
  937.     
  938.     (while tins
  939.       (icookie-refresh-tin collection (car tins))
  940.       (setq tins (cdr tins)))))
  941.  
  942.  
  943. (defun collection-set-goal-column (collection goal)
  944.   "Set goal-column for COLLECTION.
  945. Args: COLLECTION GOAL.
  946. goal-column is made buffer-local.
  947.  
  948. There will eventually be a better way to specify the cursor position."
  949.   (icookie-set-buffer-bind-dll collection 
  950.     (make-local-variable 'goal-column)
  951.     (setq goal-column goal)))
  952.  
  953.  
  954. (defun tin-goto-previous (collection pos arg)
  955.   "Move point to the ARGth previous cookie.
  956. Don't move if we are at the first cookie, or if COLLECTION is empty.
  957. Args: COLLECTION POS ARG.
  958. Returns the tin we move to."
  959.  
  960.   (icookie-set-buffer-bind-dll-let* collection
  961.       ((tin (tin-locate
  962.          collection pos (icookie-collection->last-tin collection))))
  963.  
  964.     (cond
  965.      (tin
  966.       (while (and tin (> arg 0))
  967.     (setq arg (1- arg))
  968.     (setq tin (dll-previous dll tin)))
  969.  
  970.       ;; Never step above the first cookie.
  971.  
  972.       (if (null (icookie-filter-hf collection tin))
  973.       (setq tin (dll-nth dll 1)))
  974.  
  975.       (goto-char
  976.        (icookie-wrapper->start-marker
  977.     (dll-element dll tin)))
  978.  
  979.       (if goal-column
  980.       (move-to-column goal-column))
  981.       (icookie-set-collection->last-tin collection tin)
  982.       tin))))
  983.  
  984.  
  985. (defun tin-goto-next (collection pos arg)
  986.   "Move point to the ARGth next cookie.
  987. Don't move if we are at the last cookie.
  988. Args: COLLECTION POS ARG.
  989. Returns the tin."
  990.  
  991.   ;;Need to do something clever with (current-buffer)...
  992.   ;;Previously, when the buffer was used instead of the collection, this line
  993.   ;;did the trick. No longer so... This is hard to do right! Remember that a
  994.   ;;cookie can contain a collection!
  995.   ;;(interactive (list (current-buffer) (point)
  996.   ;;             (prefix-numeric-value current-prefix-arg)))
  997.  
  998.   (icookie-set-buffer-bind-dll-let* collection
  999.       ((tin (tin-locate
  1000.          collection pos (icookie-collection->last-tin collection))))
  1001.  
  1002.     (while (and tin (> arg 0))
  1003.       (setq arg (1- arg))
  1004.       (setq tin (dll-next dll tin)))
  1005.  
  1006.     ;; Never step below the first cookie.
  1007.  
  1008.     (if (null (icookie-filter-hf collection tin))
  1009.     (setq tin (dll-nth dll -2)))
  1010.  
  1011.     (goto-char
  1012.      (icookie-wrapper->start-marker
  1013.       (dll-element dll tin)))
  1014.  
  1015.     (if goal-column
  1016.     (move-to-column goal-column))
  1017.  
  1018.     (icookie-set-collection->last-tin collection tin)
  1019.     tin))
  1020.  
  1021.  
  1022. (defun tin-goto (collection tin)
  1023.   "Move point to TIN.  Args: COLLECTION TIN."
  1024.   (icookie-set-buffer-bind-dll collection
  1025.     (goto-char
  1026.      (icookie-wrapper->start-marker
  1027.       (dll-element dll tin)))
  1028.  
  1029.     (if goal-column
  1030.     (move-to-column goal-column))))
  1031.  
  1032.  
  1033. (defun collection-collect-tin (collection predicate &rest predicate-args)
  1034.   "Select cookies from COLLECTION using PREDICATE.
  1035. Return a list of all selected tins.
  1036.  
  1037. PREDICATE is a function that takes a cookie as its first argument.
  1038.  
  1039. The tins on the returned list will appear in the same order as in the
  1040. buffer.  You should not rely on in which order PREDICATE is called.
  1041.  
  1042. Note that the buffer the COLLECTION is displayed in is current-buffer
  1043. when PREDICATE is called.  If PREDICATE must restore current-buffer if
  1044. it changes it.
  1045.  
  1046. If more than two arguments are given to collection-collect-tin the remaining
  1047. arguments will be passed to PREDICATE."
  1048.  
  1049.   (icookie-set-buffer-bind-dll-let* collection
  1050.       ((header (icookie-collection->header collection))
  1051.        (tin (dll-nth dll -2))
  1052.        (result nil))
  1053.  
  1054.     ;; Collect the tins, starting at the last one, so that they
  1055.     ;; appear in the correct order in the result (which is cons'ed
  1056.     ;; together).
  1057.  
  1058.     (while (not (eq tin header))
  1059.  
  1060.       (if (apply predicate
  1061.          (icookie-wrapper->cookie (dll-element dll tin))
  1062.          predicate-args)
  1063.       (setq result (cons tin result)))
  1064.  
  1065.       (setq tin (dll-previous dll tin)))
  1066.     result))
  1067.  
  1068.  
  1069. (defun collection-collect-cookie (collection predicate &rest predicate-args)
  1070.   "Select cookies from COLLECTION using PREDICATE.
  1071. Return a list of all selected cookies.
  1072.  
  1073. PREDICATE is a function that takes a cookie as its first argument.
  1074.  
  1075. The cookies on the returned list will appear in the same order as in
  1076. the buffer.  You should not rely on in which order PREDICATE is
  1077. called.
  1078.  
  1079. Note that the buffer the COLLECTION is displayed in is current-buffer
  1080. when PREDICATE is called.  If PREDICATE must restore current-buffer if
  1081. it changes it.
  1082.  
  1083. If more than two arguments are given to collection-collect-cookie the
  1084. remaining arguments will be passed to PREDICATE."
  1085.  
  1086.   (icookie-set-buffer-bind-dll-let* collection
  1087.       ((header (icookie-collection->header collection))
  1088.        (tin (dll-nth dll -2))
  1089.        result)
  1090.  
  1091.     (while (not (eq tin header))
  1092.  
  1093.       (if (apply predicate
  1094.          (icookie-wrapper->cookie (dll-element dll tin))
  1095.          predicate-args)
  1096.       (setq result (cons (icookie-wrapper->cookie (dll-element dll tin))
  1097.                  result)))
  1098.  
  1099.       (setq tin (dll-previous dll tin)))
  1100.     result))
  1101.  
  1102.  
  1103. (defun cookie-sort (collection predicate)
  1104.   "Sort the cookies in COLLECTION, stably, comparing elements using PREDICATE.
  1105. PREDICATE is called with two cookies, and should return T
  1106. if the first cookie is \"less\" than the second.
  1107.  
  1108. All cookies will be refreshed when the sort is complete."
  1109.  
  1110.   (icookie-set-collection->last-tin collection nil)
  1111.  
  1112.   (collection-append-cookies
  1113.    collection
  1114.    (prog1 (sort (collection-list-cookies collection) predicate)
  1115.      (collection-clear collection))))
  1116.  
  1117.  
  1118. (defun collection-buffer (collection)
  1119.   "Return the buffer that is associated with COLLECTION.
  1120. Returns nil if the buffer has been deleted."
  1121.   (let ((buf (icookie-collection->buffer collection)))
  1122.     (if (buffer-name buf)
  1123.     buf
  1124.       nil)))
  1125.  
  1126.  
  1127. ;;; Local Variables:
  1128. ;;; eval: (put 'icookie-set-buffer-bind-dll 'lisp-indent-hook 1)
  1129. ;;; eval: (put 'icookie-set-buffer-bind-dll-let* 'lisp-indent-hook 2)
  1130. ;;; End:
  1131.